## 什么是线性回归

回归（regression）是能为一个或多个自变量与因变量之间关系建模的一类方法。在自然科学和社会科学领域，回归经常用来表示输入和输出之间的关系

线性回归（Linear Regression）是一种用于研究因变量（y）和一个或多个自变量（x）之间的线性关系的统计方法

## 线性回归的形式

### 简单线性回归（只有一个变量）

基本公式：

$$
y = wx + b
$$

- $y$：目标值（因变量）
- $x$：输入特征（自变量）
- $w$：斜率（权重，weight）
- $b$：截距项（bias）

### 多元线性回归（多个输入变量）

基本公式：

$$
y = w_{1}x_{1}+w_{2}x_{2}+\dots+w_{n}x_{n}+b=\mathbf{w}^\mathrm{T}\mathbf{x} + b
$$

- $\mathbf{x} = [x_1, x_2, \ldots, x_n]^\mathrm{T}$: 特征向量
- $\mathbf{w} = [w_1, w_2, \ldots, w_n]^\mathrm{T}$: 权重向量

## 线性回归的目标：最小化误差

训练线性回归的目的是让模型的预测值 $\hat{y}$ 尽可能接近真实值 $y$

$$
\hat{y}_{i}=\mathbf{w}^\mathrm{T}\mathbf{x}_{i}+b
$$

### 损失函数：均方误差（MSE）

$$
J(w, b) = \frac{1}{m}{\sum^{m}_{i=1}(\hat{y}_{i}-y_{i})^2}
$$

- $m$：样本数量
- $\hat{y}_{i}$：第 $i$ 个样本第预测值
- $y_{i}$：真实值

我们希望平方误差的平均值越小越好

## 训练线性回归模型

### 梯度下降（Gradient Descent）

通过迭代调整 $w$ 和 $b$ 来最小化损失函数

1. 随机初始化 $w$，$b$
2. 重复以下过程直到收敛（即找到最小损失函数）
	- 计算梯度（对 $w$ 和 $b$）
	- 更新参数：
$$
w := w - \eta \frac{\partial \text{J}}{\partial w}, \quad b := b - \eta \frac{\partial \text{J}}{\partial b}
$$
### 确定一个好的学习率

- 学习率太大：步子太大，可能跳过最优解，甚至导致损失函数发散（误差越来越大）
- 学习率太小：步子太小，学习速度慢，可能需要很多次迭代才能收敛，甚至卡在局部最优解

#### 从一个小的学习率开始

通常可以从一个较小的学习率开始尝试，比如 0.01 或 0.001，然后根据训练情况调整。这是一个安全的策略，避免一开始就发散

可能尝试从 0.001 开始，每次逐渐递增三倍，观察学习曲线

#### 画出学习曲线

通过学习曲线（损失函数）来观察是否稳定下降，如果曲线平滑下降，学习率可能比较合适；如果曲线震荡或者上升，学习率可能太大；如果下降很慢，则说明太小

### 特征缩放

特征缩放（包括归一化和标准化）是一种数据预处理方法，目的是让不同特征的数值范围变得相似，避免某些特征因为数值范围大而在模型中占据主导地位

#### 为什么要使用特征缩放

梯度下降法对特征的数值范围非常敏感。如果特征的范围不一致，梯度的大小会差异很大，导致权重更新时“步子”不均匀，有的特征更新快，有的慢，模型可能需要更多迭代才能收敛，甚至可能无法收敛

### 什么时候需要进行特征缩放

通常情况下建议对所有数据进行特征缩放，以保证模型训练的公平性和稳定性，但如果特征范围本来就相似，或者算法对范围不敏感，可以不缩放

#### 算法

#### 最小 - 最大归一化

此算法的范围是 $[0, 1]$ 或者 $[-1, 1]$

$$
x' = \frac{{x - x_{min}}}{x_{max}-x_{min}}
$$
#### 标准化（Standardization, Z - Score Scaling）

$$
X'=\frac{X-\mu}{\sigma}
$$

这里 $X$ 是原始特征值，$\mu$ 是该特征的均值，$\sigma$ 是标准差

## 手写一个简单线性回归

需要使用两个库，`numpy` 和 `matplotlib`，一个用来做数学计算，另外一个用来画图，做可视化

```python
# 导入基础库并且生成模拟数据
import numpy as np
import matplotlib.pyplot as plt
```

使用 `numpy` 来生成一些模拟数据，生成用于模拟的数据集

```python
# 生成模拟数据
np.random.seed(0) # 设置随机种子
X = np.random.rand(100, 1) # 100个样本，一个特征
y = 2 * X + np.random.rand(100, 1) * 0.6 # 高斯噪声，随机点生成
```

之后来初始化和定义用于计算的一些参数和函数，下面函数的一些概念，均在上面基础篇上出现过

```python
# 初始化参数
w = np.random.randn() # 初始化一个随机的w数值，即刚开始的随机变量
b = np.random.randn()

# 学习率
lr = 0.01

# 损失函数：均方误差（ESM）
def compute_loss(y_pred, y_true):
    return np.mean((y_pred - y_true) ** 2) # 使用预测减去实际的计算损失

# 预测函数
def predict(X):
    return w * X + b # 使用原来的参数进行预测

# 梯度计算，直到收敛
def compute_gradients(X, y, y_pred):
    n = len(X)  # 样本数量
    dw = (2/n) * np.sum((y_pred - y) * X)  # 对w的梯度
    db = (2/n) * np.sum(y_pred - y)        # 对b的梯度
    return dw, db
```

正式开始训练模型

```python
# 训练模型
epochs = 3000  # 确定训练代数
for epoch in range(epochs):
    y_pred = predict(X)  # 确定预测的y数值
    loss = compute_loss(y_pred, y)  # 计算损失
    dw, db = compute_gradients(X, y, y_pred)  # 计算梯度

    # 更新参数
    w -= lr * dw
    b -= lr * db

    # 每100次打印一次参数
    if epoch % 100 == 0:
        print(f"当前迭代 {epoch}：Loss = {loss}, w = {w}, b = {b}")
```

这里的更新参数遵从：

$$
w := w - \eta \frac{\partial \text{J}}{\partial w}, \quad b := b - \eta \frac{\partial \text{J}}{\partial b}
$$

在前面使用 `compute_gradients` 函数对其进行了梯度计算

```python
dw = (2/n) * np.sum((y_pred - y) * X)
```

这个 `np.sum (...)` 就是把每个样本的梯度项全部加起来（累加），最终得到平均的梯度

最终训练的结果为

```
当前迭代 0：Loss = 3.360457010379737, w = 1.1451367960151249, b = -1.0437374740069902
当前迭代 100：Loss = 0.05000333971000506, w = 1.8210078929333788, b = 0.2606548461628321
当前迭代 200：Loss = 0.028025721164853103, w = 1.8852749691368647, b = 0.36186703590328345
当前迭代 300：Loss = 0.02777880142277748, w = 1.8986843397860884, b = 0.36593878510327527
当前迭代 400：Loss = 0.027699259584641443, w = 1.9069340963491677, b = 0.36264078935988026
当前迭代 500：Loss = 0.02763916198454221, w = 1.9138655152547173, b = 0.35919983431381447
当前迭代 600：Loss = 0.027593215478616283, w = 1.9199035531556587, b = 0.35614573793057736
当前迭代 700：Loss = 0.027558084323772734, w = 1.9251814722129978, b = 0.35347149826393925
当前迭代 800：Loss = 0.027531222670007408, w = 1.9297964397055116, b = 0.35113279056033386
当前迭代 900：Loss = 0.027510683964356013, w = 1.9338318480268912, b = 0.3490877531373423
当前迭代 1000：Loss = 0.027494979851960415, w = 1.9373604894195964, b = 0.3472995292155738
当前迭代 1100：Loss = 0.027482972320895915, w = 1.9404460045107752, b = 0.34573587000715716
当前迭代 1200：Loss = 0.027473791235162914, w = 1.943144041104862, b = 0.3443685748584561
当前迭代 1300：Loss = 0.02746677127953805, w = 1.9455032586954581, b = 0.3431729844142777
当前迭代 1400：Loss = 0.027461403746938083, w = 1.9475662061191967, b = 0.3421275360263249
当前迭代 1500：Loss = 0.02745729967452634, w = 1.9493700889361771, b = 0.3412133748793251
当前迭代 1600：Loss = 0.02745416165718836, w = 1.950947440438186, b = 0.3404140139426129
当前迭代 1700：Loss = 0.027451762295816154, w = 1.9523267083914417, b = 0.3397150366241853
当前迭代 1800：Loss = 0.02744992771863752, w = 1.9535327680962429, b = 0.33910383676535083
当前迭代 1900：Loss = 0.027448524981449462, w = 1.9545873710166628, b = 0.338569391286805
当前迭代 2000：Loss = 0.02744745243370295, w = 1.9555095370714028, b = 0.3381020613857766
当前迭代 2100：Loss = 0.027446632352308983, w = 1.9563158976608237, b = 0.33769341869880004
当前迭代 2200：Loss = 0.027446005309368537, w = 1.957020995616696, b = 0.3373360932949432
当前迭代 2300：Loss = 0.027445525865679117, w = 1.9576375474843057, b = 0.3370236407580188
当前迭代 2400：Loss = 0.027445159277944046, w = 1.958176672867222, b = 0.3367504259605912
...
当前迭代 2600：Loss = 0.02744466466310397, w = 1.9590603159625233, b = 0.33630261849885573
当前迭代 2700：Loss = 0.027444500793311913, w = 1.9594207703080337, b = 0.33611994956114444
当前迭代 2800：Loss = 0.027444375496729134, w = 1.9597359588546208, b = 0.3359602201593469
当前迭代 2900：Loss = 0.02744427969363169, w = 1.960011566074055, b = 0.3358205495502205
```

下面进行可视化

```python
# 可视化
plt.scatter(X, y, label='Data')
plt.plot(X, predict(X), color='red', label='Fitted Line')
plt.legend()
plt.title("Simple Linear Regression")
plt.xlabel("x")
plt.ylabel("y")
plt.show()
```

![](https://pic.050613.xyz/2025/04/3ccc95ac5b885b197cb92d2629b16b2e.webp)

### Q&A

#### 如何确定学习率（Lr）

1. 一般来说按照经验值，从 0.1 和 0.01 开始
2. 调参实验：试几个值比如 0.001、0.01、0.1、0.5 比较 loss 收敛情况。
3. 画出 loss 曲线：观察 loss 曲线的趋势是否收敛平稳
4. 学习率衰减（进阶）：开始时大、逐渐减小

#### 如何确定要迭代（Epochs）多少次

1. 看 Loss 是否还在下降，如果 loss 曲线已经趋于平稳，说明训练快结束了
2. 设置最大值，如 1000 次，人工设一个上限，比如 100、500、1000
3. 提前停止（Early Stopping），如果连续 N 次迭代 loss 没下降，就提前结束
4. 使用验证集监控过拟合，如果在验证集上效果开始变差，就说明模型训练太久了